﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UvsChess;

namespace StudentAI
{
    public class Heuristics
    {
        int pawnValue, rookValue, bishopValue, knightValue, queenValue, kingValue;
        public static int CHECKMATE = 2000000000;
        public static bool IN_CHECKMATE = false;
        public Heuristics(int pValue = 3, int rValue = 15, int bValue = 9, int nValue = 9, int qValue = 27, int kValue = 9999) {
            pawnValue = pValue;
            rookValue = rValue;
            bishopValue = bValue;
            knightValue = nValue;
            queenValue = qValue;
            kingValue = kValue;
        }//End Constructor

        int getPieceValue(ChessPiece piece) { 
            switch(piece){
                case ChessPiece.Empty:
                    return 0;
                case ChessPiece.BlackBishop:
                case ChessPiece.WhiteBishop:
                    return bishopValue;
                case ChessPiece.BlackKing:
                case ChessPiece.WhiteKing:
                    return kingValue;
                case ChessPiece.BlackKnight:
                case ChessPiece.WhiteKnight:
                    return knightValue;
                case ChessPiece.BlackPawn:
                case ChessPiece.WhitePawn:
                    return pawnValue;
                case ChessPiece.BlackQueen:
                case ChessPiece.WhiteQueen:
                    return queenValue;
                case ChessPiece.BlackRook:
                case ChessPiece.WhiteRook:
                    return rookValue;
                default:
                    //Do Nothing, Shouldn't get here
                    break;
            }
            return 0;
        }//End getPieceValue

        public delegate int HeuristicFunction(ChessBoard board, ChessColor color, ChessMove move);

     

        public int basicSumScore(ChessBoard board, ChessColor color)
        {
            int sum = 0;
            List<ChessMove> moves = MoveGeneration.getAllMoves(board, ChessUtils.getOppositeColor(color));
            //moves.RemoveAll(move => MoveValidation.putsKingInCheck(board, move, ChessUtils.getOppositeColor(color)));
            if (moves.Count == 0 && MoveValidation.kingInCheck(board, MoveValidation.findKing(board, ChessUtils.getOppositeColor(color)), ChessUtils.getOppositeColor(color)))
            {
                sum = CHECKMATE;
                IN_CHECKMATE = true;
                return sum;
            }
            for (int y = 0; y < ChessBoard.NumberOfRows; y++)
            {
                for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
                {
                    ChessPiece currentPiece = board[x, y];
                    if (MoveValidation.SameColor(currentPiece, color))
                    {
                        if (color == ChessColor.White && currentPiece == ChessPiece.WhitePawn && (y == 0 || y == 7))
                            sum += 8;
                        if (color == ChessColor.Black && currentPiece == ChessPiece.BlackPawn && (y == 0 || y == 7))
                            sum += 8;
                        sum += getPieceValue(currentPiece);
                    }
                    else //Opposite color = minus value 
                    {
                        if (color == ChessColor.White && currentPiece == ChessPiece.WhitePawn
                            && (y == 0 || y == 7))
                            sum -= 8;
                        if (color == ChessColor.Black && currentPiece == ChessPiece.BlackPawn
                            && (y == 0 || y == 7))
                            sum -= 8;
                        sum -= getPieceValue(currentPiece);
                    }
                    
                }  
            }
            //points for pawn promotion
            
            return sum;
        }//End basicSumScore

        public int MikesSumScore(ChessBoard board, ChessColor color, ChessMove move)
        {
            int sum = 0;
            List<ChessMove> moves = MoveGeneration.getAllMoves(board, ChessUtils.getOppositeColor(color));
            //moves.RemoveAll(move => MoveValidation.putsKingInCheck(board, move, ChessUtils.getOppositeColor(color)));
            if (moves.Count == 0 && MoveValidation.kingInCheck(board, MoveValidation.findKing(board, ChessUtils.getOppositeColor(color)), ChessUtils.getOppositeColor(color)))
            {
                sum = CHECKMATE;
                IN_CHECKMATE = true;
                return sum;
            }
            else if(moves.Count == 0)
            {
                sum -= 9999;
                return sum;
            }
            for (int y = 0; y < ChessBoard.NumberOfRows; y++)
            {
                for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
                {
                    ChessPiece currentPiece = board[x, y];
                    if (MoveValidation.SameColor(currentPiece, color))
                    {
                        if (color == ChessColor.White && currentPiece == ChessPiece.WhitePawn && (y == 0 || y == 7))
                            sum += 8;
                        if (color == ChessColor.Black && currentPiece == ChessPiece.BlackPawn && (y == 0 || y == 7))
                            sum += 8;
                        sum += getPieceValue(currentPiece);
                    }
                    else //Opposite color = minus value 
                    {
                        if (color == ChessColor.White && currentPiece == ChessPiece.WhitePawn
                            && (y == 0 || y == 7))
                            sum -= 8;
                        if (color == ChessColor.Black && currentPiece == ChessPiece.BlackPawn
                            && (y == 0 || y == 7))
                            sum -= 8;
                        sum -= getPieceValue(currentPiece);
                    }

                }
            }
            //points for pawn promotion
            
            ChessPiece currPiece = board[move.To];
            Random r = new Random(Environment.TickCount);
            int lucky = r.Next() % 3;
            if (MoveValidation.SameColor(currPiece, color) && lucky == 1 || lucky == 2)
            {
                if (color == ChessColor.Black && currPiece == ChessPiece.BlackPawn
                          )
                    sum += 1;
                if (color == ChessColor.White && currPiece == ChessPiece.WhitePawn
                          )
                    sum += 1;
            }
            else
            {
                if (color == ChessColor.Black && currPiece == ChessPiece.BlackPawn
                          && lucky == 1 || lucky == 2)
                    sum -= 1;
                if (color == ChessColor.White && currPiece == ChessPiece.WhitePawn
                          && lucky == 1 || lucky == 2)
                    sum -= 1;
            }
            return sum;
        }//End basicSumScore


        /// <summary>
        /// Basic Sum Score with King movement restriction when enemy color has fewer pieces.
        /// </summary>
        /// <param name="board"></param>
        /// <param name="color"></param>
        /// <returns></returns>
        public int sumScoreEnemyKingRestricted(ChessBoard board, ChessColor color) {
            int mySum = 0;
            int enemySum = 0;
            int totalScore = 0;
            ChessLocation enemyKingLocation = MoveValidation.findKing(board, ChessUtils.getOppositeColor(color));
            List<ChessMove> moves = MoveGeneration.getAllMoves(board, ChessUtils.getOppositeColor(color));
            //moves.RemoveAll(move => MoveValidation.putsKingInCheck(board, move, ChessUtils.getOppositeColor(color)));
            if (moves.Count == 0)
            {
                if (MoveValidation.kingInCheck(board, enemyKingLocation, ChessUtils.getOppositeColor(color)))
                {
                    mySum = CHECKMATE;
                    IN_CHECKMATE = true;
                    return mySum;
                }
            }
            int xDiff, yDiff, manhattanDiff = 0;
            for (int y = 0; y < ChessBoard.NumberOfRows; y++)
            {
                for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
                {
                    ChessPiece currentPiece = board[x, y];
                    if (MoveValidation.SameColor(currentPiece, color))
                    {
                        xDiff = Math.Abs(x - enemyKingLocation.X);
                        yDiff = Math.Abs(y - enemyKingLocation.Y);
                        if( xDiff > 1 && yDiff > 1)
                            manhattanDiff += (xDiff + yDiff);
                        mySum += getPieceValue(currentPiece);
                    }
                    else //Opposite color = minus value 
                    {
                        enemySum += getPieceValue(currentPiece);
                    }

                }
            }
            if (enemySum >= kingValue && enemySum <= kingValue + pawnValue * 6) {
                totalScore += 4 * (8 - MoveGeneration.generateKingMoves(board, MoveValidation.findKing(board, ChessUtils.getOppositeColor(color)), ChessUtils.getOppositeColor(color)).Count);
            }
            totalScore = totalScore + mySum - enemySum - manhattanDiff;

            return totalScore;
        }

        /// <summary>
        /// Basic Sum Score with King movement restriction when enemy color has fewer pieces.
        /// </summary>
        /// <param name="board"></param>
        /// <param name="color"></param>
        /// <returns></returns>
        public int sumScoreBackupBonus(ChessBoard board, ChessColor color)
        {
            int mySum = 0;
            int enemySum = 0;
            int totalScore = 0;
            ChessLocation enemyKingLocation = MoveValidation.findKing(board, ChessUtils.getOppositeColor(color));
            List<ChessMove> moves = MoveGeneration.getAllMoves(board, ChessUtils.getOppositeColor(color));            
            if (moves.Count == 0)
            {
                if (MoveValidation.kingInCheck(board, enemyKingLocation, ChessUtils.getOppositeColor(color)))
                {
                    mySum = CHECKMATE;
                    IN_CHECKMATE = true;
                    return mySum;
                }
            }
            int xDiff, yDiff;
            List<ChessLocation> myPieces = new List<ChessLocation>();
            for (int y = 0; y < ChessBoard.NumberOfRows; y++)
            {
                for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
                {
                    ChessPiece currentPiece = board[x, y];
                    if (MoveValidation.SameColor(currentPiece, color))
                    {
                        myPieces.Add(new ChessLocation(x, y));
                        xDiff = Math.Abs(x - enemyKingLocation.X);
                        yDiff = Math.Abs(y - enemyKingLocation.Y);
                        
                        mySum += getPieceValue(currentPiece);
                    }
                    else //Opposite color = minus value 
                    {
                        enemySum += getPieceValue(currentPiece);
                    }

                }
            }
            for (int i = 0; i < myPieces.Count; ++i) {
                for (int j = 0; j < myPieces.Count; ++j) {
                    //if (j == i)
                    //{
                    //    //Don't do compare same piece
                    //}
                    //else 
                    //{ 
                    //    if( MoveValidation
                    //}
                }
                //If this piece is backed up, add points
            }
            if (enemySum >= kingValue && enemySum <= kingValue + pawnValue * 6)
            {
                totalScore += 4 * (8 - MoveGeneration.generateKingMoves(board, MoveValidation.findKing(board, ChessUtils.getOppositeColor(color)), ChessUtils.getOppositeColor(color)).Count);
            }
            totalScore = totalScore + mySum - enemySum;

            return totalScore;
        }

        public ChessMove minimax(ChessBoard root, ChessColor myColor, HeuristicFunction utilityFunction, int maxDepth=5)
        {            
            Stack<Node> stack = new Stack<Node>();
            Node currentNode = new Node(null, root, null, myColor, 0);
            stack.Push(currentNode);
            List<Node> bestMoves = new List<Node>();
            int iterations = 0;
            Int64 timeElapsed = StudentAI.stopwatch.ElapsedMilliseconds;
            while (stack.Count != 0 )//&& timeElapsed <= 4800) // && !StudentAI.studentAI.IsMyTurnOver() )
            {                
                iterations++;
                currentNode = stack.Peek();
                StudentAI.studentAI.Log(currentNode.depth + ": " + currentNode.GetHashCode() + " - " + currentNode.score);
                //if max depth has been reached generate a score
                if (currentNode.depth >= maxDepth)
                    currentNode.score = utilityFunction(currentNode.currBoard, myColor, currentNode.move);
                else if(currentNode.score == null)
                {
                    List<ChessMove> moves = MoveGeneration.getAllMoves(currentNode.currBoard, currentNode.color);            
                    //moves.RemoveAll(move => MoveValidation.putsKingInCheck(currentNode.currBoard, move, currentNode.color));
                    if (moves.Count == 0)
                    {
                        if (currentNode.color == myColor) 
                        {
                            int score = -9999;
                            if( currentNode.betterThanParentScore( score, myColor )) {
                                currentNode.parent.score = score;
                            }
                            stack.Pop();
                            continue;
                        }
                        else if (currentNode.color != myColor)
                        {
                            int score = 99999999;
                            if (currentNode.betterThanParentScore(score, myColor)) {
                                currentNode.parent.score = score;
                            }
                            stack.Pop();
                            continue;
                        }
                    }
                    //get all children
                    foreach(var move in moves)
                    {
                        ChessBoard newBoard = currentNode.currBoard.Clone();
                        newBoard.MakeMove(move);
                        stack.Push(new Node(currentNode,newBoard,move,ChessUtils.getOppositeColor(currentNode.color),
                            currentNode.depth + 1));
                    }
                }
                
                if (currentNode.score != null)
                {
                    if (currentNode.depth == 0)
                        break;
                        //return finalMove;
                                        
                    if(currentNode.depth > 0 && currentNode.betterThanParentScore(currentNode.score,myColor))
                    {
                        currentNode.parent.score = currentNode.score;
                    }
                    //if(currentNode.depth == 1 && finalMove == null)
                    if( currentNode.depth == 1 && bestMoves.Count == 0)
                    {
                        //finalMove = currentNode.move;
                        //bestMoveScore = currentNode.score;
                        bestMoves.Add(currentNode);
                    }
                    //else if(currentNode.depth == 1 && bestMoveScore < currentNode.score)
                    else if (currentNode.depth == 1 && bestMoves[0].score < currentNode.score)
                    {
                        //finalMove = currentNode.move;
                        //bestMoveScore = currentNode.score;
                        bestMoves.Clear();
                        bestMoves.Add(currentNode);
                    }
                    else if (currentNode.depth == 1 && bestMoves[0].score == currentNode.score)
                    {
                        bestMoves.Add(currentNode);
                    }
                    
                    stack.Pop();
                }
                timeElapsed = StudentAI.stopwatch.ElapsedMilliseconds;
            }
            //return finalMove;
            if (bestMoves.Count == 0)
            {
                return null;
            }
            else 
            {
                Random r = new Random(Environment.TickCount);
                return bestMoves[r.Next() % bestMoves.Count].move;
            }

        }
        
        public ChessMove greedy(ChessBoard board, ChessColor color, List<ChessMove> moves, HeuristicFunction function) {
            ChessMove best = null;
            int bestValue = int.MinValue;
            ChessBoard newBoard;
            foreach (ChessMove move in moves) {
                if (best == null)
                {
                    best = move;
                }
                else 
                {
                    newBoard = new ChessBoard(board.ToPartialFenBoard());
                    newBoard.MakeMove(move);
                    if (function(newBoard, color, null) > bestValue) {
                        best = move;
                    }
                }
            }
            return best;
        }

        public ChessMove greedyRandomized(ChessBoard board, ChessColor color, List<ChessMove> moves, HeuristicFunction function)
        {
            List<ChessMove> best = new List<ChessMove>();
            int bestValue = int.MinValue;
            ChessBoard newBoard;
            foreach (ChessMove move in moves)
            {
                if (best.Count == 0)
                {
                    best.Add(move);
                }
                else
                {
                    newBoard = new ChessBoard(board.ToPartialFenBoard());
                    newBoard.MakeMove(move);
                    int currentValue = function(newBoard, color, null);
                    if (currentValue == CHECKMATE)
                        return move;
                    if ( currentValue > bestValue)
                    {
                        bestValue = currentValue;
                        best.Clear();
                        best.Add(move);
                    }
                    else if (currentValue == bestValue) {                        
                        best.Add(move);
                    }
                }
            }            
            Random r = new Random(Environment.TickCount);
            if (best.Count == 0)
                return null;
            return best[r.Next()%best.Count];
        }

    }//End Class Heuristics
}
